﻿using DXBase;
using DXBase.D3D;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI;
using DXBase.Samples;
using DXBase.Structs;

namespace DXBase.Samples
{
	public class Universes
	{
		public async static Task<Universe> CreateUniverse1(DXContext ctxt, SharedData data = null)
		{
			data = data ?? new SharedData(ctxt);
			var cubetex = await CreateSceneTexture(ctxt);

			var u = new Universe
			{
				Name = "Floating Bodies",
				Background = Colors.Aquamarine,
				CameraController = { ModelTransform = { Origin = new Vector3F(0, 0, 50) }, },
				PixelShader = data.BasicPixelShader,
				VertexShader = data.BasicVertexShader,
				Bodies =
				{
					new SpaceBody
					{
						Scale = new Vector3F(10, 10, 10),
						Location = new Vector3F(0, 0, 50),
						UpdateAction = (me, pt) =>
						{
							me.Rotation = QuaternionF.Rotation(0, 1, 0, -36 * pt.Total);
						},
						Shape = data.Cube,
						Texture = cubetex,
						FinalTransform = { LeftHanded = data.Cube.LeftHanded },
					},
					new SpaceBody
					{
						Scale = new Vector3F(8, 6, 8),
						UpdateAction = (me, pt) =>
						{
							me.Rotation = QuaternionF.Rotation(0, 1, 0, -96 * pt.Total);
							me.Location = new Vector3F(15, 0, 0).Rotate(0, 1, 0, -24 * pt.Total) + new Vector3F(0, 15, 50);
						},
						Shape = data.Sphere,
						Texture = data.GetTexture("DXBase.Utils\\Samples\\earth600.jpg"),
						Satellites = 
						{
							new SpaceBody
							{
								UpdateAction = (me, pt) =>
								{
									me.Rotation = QuaternionF.Rotation(0, 1, 0, -84 * pt.Total);
									me.Location = new Vector3F(12, 0, 0).Rotate(0, 1, 0, -24 * pt.Total);
								},
								Shape = data.Sphere,
								Texture = data.Earth,
							},
						},
					},
					new SpaceBody
					{
						Scale = new Vector3F(6, 5, 6),
						Location = new Vector3F(-15, -15, 55),
						UpdateAction = (me, pt) =>
						{
							me.Rotation = QuaternionF.Rotation(0, 1, 0, -48 * pt.Total);
						},
						Shape = data.Sphere,
					},
				},
			};

			u.Reset();
			return u;
		}
		public async static Task<Texture2D> CreateSceneTexture(DXContext ctxt)
		{
			var tex = new Texture2D();
			tex.Create(300, 300);
			ctxt.Target = tex;

			var scene = new DXBase.Samples.Scene(ctxt);
			scene.Background = Windows.UI.Colors.DarkGoldenrod;
			scene.Add(new DXBase.Samples.CubeRenderer());
			scene.Add(new DXBase.Samples.HelloDWrite());
			await scene.LoadAsync();
			scene.RenderFrame();

			return tex;
		}

		public static Universe CreateUniverse2(DXContext ctxt, SharedData data = null)
		{
			data = data ?? new SharedData(ctxt);

			var u = new Universe
			{
				Name = "TXT Models",
				Background = Colors.Aquamarine,
				CameraController = { ModelTransform = { Origin = new Vector3F(0, 0, 150) }, },
				NumUsedDirectionLights = 1,
				NumUsedPointLights = 1,
				NumUsedSpotLights = 1,
				DirLights = 
				{
					new DirectionalLight
					{
						ambient   = new Vector4F(0.2f, 0.2f, 0.2f, 1),
						diffuse   = new Vector4F(0.5f, 0.5f, 0.5f, 1),
						specular  = new Vector4F(0.5f, 0.5f, 0.5f, 1),
						direction = new Vector3F(0.57735f, -0.57735f, 0.57735f).Normalize(),
					},
				},
				PointLights =
				{
					new PointLight
					{
						ambient     = new Vector4F(0.3f, 0.3f, 0.3f, 1),
						diffuse     = new Vector4F(0.7f, 0.7f, 0.7f, 1),
						specular    = new Vector4F(0.7f, 0.7f, 0.7f, 1),
						attenuation = new Vector3F(0, 0.1f, 0),
						range       = 25,
					},
				},
				SpotLights = 
				{
					new SpotLight
					{
						ambient     = new Vector4F(0.2f,0.2f,0.2f,1),
						diffuse     = new Vector4F(1,1,0,1),
						specular    = new Vector4F(3,2.8f,1,1),
						attenuation = new Vector3F(0,0.03f,0),
						spot = 2,
						range = 10000,
					},
				},
				UpdateActions = 
				{
					(me, pt) =>
					{
						var pl = me.PointLights[0];
						pl.position = me.Bodies[0].Location + new Vector3F(0, 30, 0);
						me.PointLights[0] = pl;

						var sl = me.SpotLights[0];
						sl.position = me.CameraController.Camera.EyeLocation;
						sl.direction = me.CameraController.Camera.LookVector.Normalize();
						me.SpotLights[0] = sl;
					},
				},
				PixelShader = data.BasicPixelShader,
				VertexShader = data.BasicVertexShader,
				Bodies =
				{
					new SpaceBody
					{
						Scale = new Vector3F(10,10,10),
						Location = new Vector3F(0, 0, 150),
						UpdateAction = (me, pt) =>
						{
							me.Rotation = QuaternionF.Rotation(0, 1, 0, -36 * pt.Total);
						},
						Satellites =
						{
							new SpaceBody
							{
								Scale = new Vector3F(8, 8, 8),
								UpdateAction = (me, pt) =>
								{
									me.Rotation = QuaternionF.Rotation(0, 1, 0, -84 * pt.Total);
									me.Location = new Vector3F(40, 0, 0).Rotate(0, 1, 0, -24 * pt.Total);
								},
								Material = new Material
								{
									ambient  = new Vector4F(0.48f, 0.77f, 0.16f, 1),
									diffuse  = new Vector4F(0.48f, 0.77f, 0.16f, 1),
									specular = new Vector4F(0.88f, 0.88f, 0.88f, 1),
								},
							},
							new SpaceBody
							{
								Scale = new Vector3F(8, 8, 8),
								UpdateAction = (me, pt) =>
								{
									me.Rotation = QuaternionF.Rotation(0, 1, 0, -84 * pt.Total);
									me.Location = new Vector3F(-40, 0, 0).Rotate(0, 1, 0, -24 * pt.Total);
								},
								Material = new Material
								{
									ambient  = new Vector4F(0.48f, 0.27f, 0.66f, 1),
									diffuse  = new Vector4F(0.78f, 0.67f, 0.76f, 1),
									specular = new Vector4F(0.88f, 0.88f, 0.88f, 96),
								},
							},
						}
					},
				},
			};

			data.ShapeSkull.ContinueWith(tb => u.Bodies[0].Satellites[0].Shape = tb.Result);
			data.ShapeCar.ContinueWith(tb => u.Bodies[0].Satellites[1].Shape = tb.Result);

			u.Reset();
			return u;
		}

		public static Universe CreateUniverse3(DXContext ctxt, SharedData data = null)
		{
			data = data ?? new SharedData(ctxt);

			uint DIV = 100;
			var meshdata = MeshLoader.CreateGrid32(60, 60, DIV, DIV);
			var ppoints = new PointerBasicVertex(meshdata.Vertices[0]);
			var N = ppoints.Count;
			Action<Universe, PerformanceTimer> updMeshData = (mu, pt) =>
			{
				float t = pt.Total;
				Func<float, float, float> getHeight = (x, z) => 
				{
					return (float)(0.3 * (z * Math.Sin(0.2 * x + t) + x * Math.Cos(0.3 * z + 2 * t))); 
				};
				Func<float, Color4B> getColor = v =>
				{
					if (v < -10)
						return new Color4B(255, 245, 140, 255);
					if (v < 5)
						return new Color4B(123, 196, 123, 255);
					if (v < 12)
						return new Color4B(25, 125, 48, 255);
					if (v < 20)
						return new Color4B(116, 120, 90, 255);
					return new Color4B(255, 255, 255, 255);
				};

				Stopwatch sw = new Stopwatch();
				sw.Start();
				Parallel.For(0, (int)DIV, j =>
				{
					int i0 = (int)(j * DIV);
					int i1 = (int)(i0 + DIV);
					if (i1 > N)
						i1 = N;
					for (int i = i0; i < i1; i++)
					{
						var d = ppoints[i];
						d.Pos.Y = getHeight(d.Pos.X, d.Pos.Z);
						d.Color = getColor(d.Pos.Y);
						ppoints[i] = d;
					}
				});
				sw.Stop();
				Debug.WriteLine("Ellapsed Ticks: " + sw.ElapsedTicks);
				meshdata.VerticesBuffer[0].UpdateBuffer();
			};

			var u = new Universe
			{
				Name = "Animated Vertex",
				Background = Colors.Aquamarine,
				RasterizerState = 
				{
					CULL_MODE = CULL_MODE.NONE,
					Wireframe = true,
				},
				NumUsedDirectionLights = 1,
				NumUsedPointLights = 1,
				NumUsedSpotLights = 1,
				DirLights = 
				{
					new DirectionalLight
					{
						ambient = new Vector4F(0.2f, 0.2f, 0.2f, 1),
						diffuse = new Vector4F(0.5f, 0.5f, 0.5f, 1),
						specular = new Vector4F(0.5f, 0.5f, 0.5f, 1),
						direction = new Vector3F(0.57735f, -0.57735f, 0.57735f).Normalize(),
					},
				},
				PointLights = 
				{
					new PointLight
					{
						ambient = new Vector4F(0.3f, 0.3f, 0.3f, 1),
						diffuse = new Vector4F(0.7f, 0.7f, 0.7f, 1),
						specular = new Vector4F(0.7f, 0.7f, 0.7f, 1),
						attenuation = new Vector3F(0, 0.1f, 0),
						range = 25,
					},
				},
				SpotLights = 
				{
					new SpotLight
					{
						ambient = new Vector4F(0, 0, 0, 1),
						diffuse = new Vector4F(1, 1, 0, 1),
						specular = new Vector4F(1, 1, 1, 1),
						attenuation = new Vector3F(0, 0.2f, 0),
						spot = 2,
						range = 10000,
					},
				},
				UpdateActions = 
				{
					updMeshData,
					(me, pt) =>
					{
						var pl = me.PointLights[0];
						pl.position = me.Bodies[0].Location + new Vector3F(0, 30, 0);
						me.PointLights[0] = pl;

						var sl = me.SpotLights[0];
						sl.position = me.CameraController.Camera.EyeLocation;
						sl.direction = me.CameraController.Camera.LookVector.Normalize();
						me.SpotLights[0] = sl;
					},
				},
				CameraController = { ModelTransform = { Origin = new Vector3F(0, 0, 150) }, },
				PixelShader = data.BasicPixelShader,
				VertexShader = data.BasicVertexShader,
				Bodies =
				{
					new SpaceBody
					{
						Location = new Vector3F(0, 0, 150),
						Satellites =
						{
							new SpaceBody
							{
								Rotation =  QuaternionF.Rotation(1,0,0,-45),
								Shape = meshdata,
								Material = new Material
								{
									ambient = new Vector4F(0.137f, 0.42f, 0.556f, 1),
									diffuse = new Vector4F(0.137f, 0.42f, 0.556f, 1),
									specular = new Vector4F(0.88f, 0.88f, 0.88f, 96),
								},
							},
						}
					},
				},
			};

			u.Reset();
			return u;
		}

		public static Universe CreateUniverse4(DXContext ctxt, SharedData data = null)
		{
			data = data ?? new SharedData(ctxt);

			var box = MeshLoader.CreateBox16(new Vector3F(1, 1, 1));
			var grid = MeshLoader.CreateGrid32(20, 30, 20, 20);
			var gsphere = MeshLoader.CreateGeosphere32(1, 2);
			var cylinder = MeshLoader.CreateCylinder16(0.5f, 0.3f, 3, 20, 20);

			var floor = data.Floor;
			var bricks = data.Bricks;
			var stone = data.Stone;

			Vector3F O = new Vector3F { Z = 30 };
			var mat = new Material
			{
				ambient = new Vector4F(1,1,1,1),
				diffuse = new Vector4F(1,1,1,1),
				specular = new Vector4F(0,0,0,1),
			};

			var u = new Universe
			{
				Name = "Temple",
				Background = Colors.DarkBlue,
				CameraController = { ModelTransform = { Origin = O }, },
				PixelShader = data.BasicPixelShader,
				VertexShader = data.BasicVertexShader,
				NumUsedDirectionLights = 1,
				DirLights = 
				{
					new DirectionalLight
					{
						ambient   = new Vector4F(0.3f, 0.3f, 0.3f, 1),
						diffuse   = new Vector4F(1.0f, 0.95f, 0.85f, 1),
						specular  = new Vector4F(0.0f, 0.0f, 0.0f, 1),
						direction = new Vector3F(1, -1, 0).Normalize(),
					},
				},
				Bodies =
				{
					new SpaceBody
					{
						Location = O,
						Satellites =
						{
							new SpaceBody
							{
								Shape = grid,
								Texture = floor,
								Material = mat,
							},
							new SpaceBody
							{
								Scale = new Vector3F(3,1,3),
								Location = new Vector3F { Y = 0.5f },
								Shape = box,
								Texture = stone,
								Material = mat,
							},
						}
					},
				},
			};
			var root = u.Bodies[0];
			for (int i = 0; i < 5; i++)
			{
				root.Satellites.Add(new SpaceBody
				{
					Location = new Vector3F(-5, 4, -10 + i * 5),
					Shape = gsphere,
					Texture = stone,
					Material = mat,
				});
				root.Satellites.Add(new SpaceBody
				{
					Location = new Vector3F(+5, 4, -10 + i * 5),
					Shape = gsphere,
					Texture = stone,
					Material = mat,
				});
				root.Satellites.Add(new SpaceBody
				{
					Location = new Vector3F(-5, 1.5f, -10 + i * 5),
					Shape = cylinder,
					Texture = bricks,
					Material = mat,
				});
				root.Satellites.Add(new SpaceBody
				{
					Location = new Vector3F(+5, 1.5f, -10 + i * 5),
					Shape = cylinder,
					Texture = bricks,
					Material = mat,
				});
			}

			u.Reset();
			return u;
		}

		public class SharedData
		{
			public SharedData(DXContext context) { Context = context; }
			public DXContext Context { get; private set; }

			#region GetTexture(), Earth

			public Texture2D GetTexture(string file)
			{
				Texture2D tex;
				if (!textures.TryGetValue(file, out tex))
				{
					textures[file] = tex = new Texture2D();
					tex.LoadFile(file);
				}
				return tex;
			}
			Dictionary<string, Texture2D> textures = new Dictionary<string,Texture2D>();

			public Texture2D Earth { get { return GetTexture("DXBase.Utils\\Samples\\earth600.jpg"); } }
			public Texture2D Stone { get { return GetTexture("DXBase.Utils\\Samples\\stone.dds"); } }
			public Texture2D Bricks { get { return GetTexture("DXBase.Utils\\Samples\\bricks.dds"); } }
			public Texture2D Floor { get { return GetTexture("DXBase.Utils\\Samples\\floor.dds"); } }

			#endregion

			#region ShapeCar, ShapeSkull, Sphere, Cube

			public Task<Shape> ShapeCar
			{
				get
				{
					if (shapeCar == null)
					{
						shapeCar = MeshLoader
							.LoadFrankLunaTextModel16("DXBase.Utils\\Samples\\car.txt")
							.AsTask()
							.ContinueWith(mesh => (Shape)mesh.Result);
					}
					return shapeCar;
				}
			}
			Task<Shape> shapeCar;

			public Task<Shape> ShapeSkull
			{
				get
				{
					if (shapeSkull == null)
					{
						shapeSkull = MeshLoader
							.LoadFrankLunaTextModel16("DXBase.Utils\\Samples\\skull.txt")
							.AsTask()
							.ContinueWith(mesh => (Shape)mesh.Result);
					}
					return shapeSkull;
				}
			}
			Task<Shape> shapeSkull;

			public Shape Sphere
			{
				get
				{
					if (sphere == null)
					{
						//sphere = MeshLoader.CreateSphere();
						sphere = MeshLoader.CreateGeosphere32(1, 3);
					}
					return sphere;
				}
			}
			Shape sphere;
			public Shape Cube
			{
				get
				{
					if (cube == null)
					{
						cube = MeshLoader.CreateCube16();
					}
					return cube;
				}
			}
			Shape cube;

			#endregion

			#region shaders: PlainPixelShader, PlainVertexShader, TexPixelShader, TexVertexShader

			public PixelShader BasicPixelShader
			{
				get
				{
					if (colorPShader == null)
					{
						colorPShader = new PixelShader();
						colorPShader.LoadShaderFile("DXBase.Utils.Shaders\\BasicPixelShader.cso");
					};
					return colorPShader;
				}
			}
			PixelShader colorPShader;

			public VertexShader BasicVertexShader
			{
				get
				{
					if (tBVShader == null)
					{
						tBVShader = new VertexShader();
						tBVShader.LoadShaderFile(BasicVertex.GetLayout(), "DXBase.Utils.Shaders\\BasicVertexShader.cso");
					}
					return tBVShader;
				}
			}
			VertexShader tBVShader;

			#endregion
		}

		public static async Task CreateMultiverse(ObservableCollection<Universe> multiverse, DXContext ctxt, InputController input = null)
		{
			var data = new SharedData(ctxt);

			Func<Universe, Universe> init = u =>
			{
				u.CameraController.InputController = input;
				return u;
			};

			Stopwatch sw = new Stopwatch();

			sw.Restart();
			multiverse.Add(init(await CreateUniverse1(ctxt, data)));
			Debug.WriteLine("Uni 2 in " + sw.ElapsedMilliseconds + " ms.");

			sw.Restart();
			multiverse.Add(init(CreateUniverse2(ctxt, data)));
			Debug.WriteLine("Uni 3 in " + sw.ElapsedMilliseconds + " ms.");

			sw.Restart();
			multiverse.Add(init(CreateUniverse3(ctxt, data)));
			Debug.WriteLine("Uni 4 in " + sw.ElapsedMilliseconds + " ms.");

			sw.Restart();
			multiverse.Add(init(CreateUniverse4(ctxt, data)));
			Debug.WriteLine("Uni 5 in " + sw.ElapsedMilliseconds + " ms.");
		}
	}
}
